﻿using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using HandyControl.Properties.Langs;

namespace HandyControl.Tools.Extension;

public class LangExtension : MarkupExtension
{
    private readonly DependencyObject _proxy;

    public LangExtension()
    {
        _proxy = new DependencyObject();
        Source = LangProvider.Instance;
    }

    public LangExtension(string key) : this()
    {
        Key = key;
    }

    public static readonly DependencyProperty KeyProperty = DependencyProperty.RegisterAttached(
        "Key", typeof(object), typeof(LangExtension), new PropertyMetadata(default));

    public object Key
    {
        get => _proxy.GetValue(KeyProperty);
        set => _proxy.SetValue(KeyProperty, value);
    }

    private static readonly DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
        "TargetProperty", typeof(DependencyProperty), typeof(LangExtension), new PropertyMetadata(default(DependencyProperty)));

    private static void SetTargetProperty(DependencyObject element, DependencyProperty value)
        => element.SetValue(TargetPropertyProperty, value);

    private static DependencyProperty GetTargetProperty(DependencyObject element)
        => (DependencyProperty) element.GetValue(TargetPropertyProperty);

    public BindingMode Mode { get; set; }

    public IValueConverter Converter { get; set; }

    public object ConverterParameter { get; set; }

    public object Source { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget provideValueTarget)) return this;
        if (provideValueTarget.TargetObject.GetType().FullName == "System.Windows.SharedDp") return this;
        if (!(provideValueTarget.TargetObject is DependencyObject targetObject)) return this;
        if (!(provideValueTarget.TargetProperty is DependencyProperty targetProperty)) return this;

        switch (Key)
        {
            case string key:
                {
                    var binding = CreateLangBinding(key);
                    BindingOperations.SetBinding(targetObject, targetProperty, binding);
                    return binding.ProvideValue(serviceProvider);
                }
            case Binding keyBinding when targetObject is FrameworkElement element:
                {
                    if (element.DataContext != null)
                    {
                        var binding = SetLangBinding(element, targetProperty, keyBinding.Path, element.DataContext);
                        return binding.ProvideValue(serviceProvider);
                    }

                    SetTargetProperty(element, targetProperty);
                    element.DataContextChanged += LangExtension_DataContextChanged;

                    break;
                }
            case Binding keyBinding when targetObject is FrameworkContentElement element:
                {
                    if (element.DataContext != null)
                    {
                        var binding = SetLangBinding(element, targetProperty, keyBinding.Path, element.DataContext);
                        return binding.ProvideValue(serviceProvider);
                    }

                    SetTargetProperty(element, targetProperty);
                    element.DataContextChanged += LangExtension_DataContextChanged;

                    break;
                }
        }

        return string.Empty;
    }

    private void LangExtension_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        switch (sender)
        {
            case FrameworkElement element:
                {
                    element.DataContextChanged -= LangExtension_DataContextChanged;
                    if (!(Key is Binding keyBinding)) return;

                    var targetProperty = GetTargetProperty(element);
                    SetTargetProperty(element, null);
                    SetLangBinding(element, targetProperty, keyBinding.Path, element.DataContext);
                    break;
                }
            case FrameworkContentElement element:
                {
                    element.DataContextChanged -= LangExtension_DataContextChanged;
                    if (!(Key is Binding keyBinding)) return;

                    var targetProperty = GetTargetProperty(element);
                    SetTargetProperty(element, null);
                    SetLangBinding(element, targetProperty, keyBinding.Path, element.DataContext);
                    break;
                }
        }
    }

    private BindingBase SetLangBinding(DependencyObject targetObject, DependencyProperty targetProperty, PropertyPath path, object dataContext)
    {
        if (targetProperty == null) return null;

        BindingOperations.SetBinding(targetObject, targetProperty, new Binding
        {
            Path = path,
            Source = dataContext,
            Mode = BindingMode.OneWay
        });

        var key = targetObject.GetValue(targetProperty) as string;
        if (string.IsNullOrEmpty(key)) return null;

        var binding = CreateLangBinding(key);
        BindingOperations.SetBinding(targetObject, targetProperty, binding);
        return binding;
    }

    private BindingBase CreateLangBinding(string key) => new Binding(key)
    {
        Converter = Converter,
        ConverterParameter = ConverterParameter,
        UpdateSourceTrigger = UpdateSourceTrigger.Explicit,
        Source = Source,
        Mode = BindingMode.OneWay
    };
}
